﻿
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <thread>

#ifdef _WIN64
#include <winsock2.h>
#include <windows.h>
#define sleep(t) Sleep((t)*1000)
#else
#include <unistd.h>
#endif

#include "./SerialControl/SerialControl.h"
#include "./XYModem/xymodem.h"
#include "../Lib/cmdline/cmdline.h"
#include "../Lib/toml11/toml.hpp"

bool SYSTEM_RUNNING = true;
SerialControl *serial_handle = nullptr;

const char * cmd1 = "";
const char * target1 = "\b\b\b";

struct command
{
    std::string send;
    std::string receive;
};

struct command_info
{
    bool cmd_type;
    std::string name;
    std::string shortcut;
    std::string brief;
    std::vector<struct command> cmds;
};

std::vector<struct command_info> cmd_infos;

#define COMMAND_FILE_FST "../../Config/command.toml"
#define COMMAND_FILE_SND "./command.toml"

void read_command_config_file(void)
{
    std::string command_file_path;
    if (FILE *file = fopen(COMMAND_FILE_FST, "r"))
    {
        fclose(file);
        command_file_path = COMMAND_FILE_FST;
    }
    else
    {
        command_file_path = COMMAND_FILE_SND;
    }

    auto command_file = toml::parse(command_file_path);

    std::vector<std::string> command_name_lists = toml::find<std::vector<std::string>>(command_file, "command_name_lists");

    for (const auto &command_name_list : command_name_lists)
    {
        // std::cout << command_name_list << std::endl;
        const auto& cmd_config = toml::find(command_file, command_name_list);

        struct command_info cmd_info;

        cmd_info.cmd_type = toml::find<bool>(cmd_config, "cmd_type");
        cmd_info.name = toml::find<std::string>(cmd_config, "name");
        if (cmd_info.cmd_type)
        {
            cmd_info.shortcut = toml::find<std::string>(cmd_config, "shortcut");
            cmd_info.brief = toml::find<std::string>(cmd_config, "brief");
        }
        else
        {
            continue;
        }

        const auto &cmd_tables = toml::find<toml::array>(command_file, command_name_list, "command");
        for (const auto &cmd_table : cmd_tables)
        {
            struct command cmd;
            cmd.send = toml::find<std::string>(cmd_table, "send");
            cmd.receive = toml::find<std::string>(cmd_table, "receive");
            cmd_info.cmds.push_back(cmd);
        }

        cmd_infos.push_back(cmd_info);
    }
}

bool send_cmd_wait_response(std::string cmd, const std::string& target, int wait_line, int wait_time)
{
    if (!cmd.empty())
    {
        serial_handle->send_cmd(cmd);
    }
    std::string rec_line;
    int count = 0;
    while (SYSTEM_RUNNING)
    {
        serial_handle->read_data(rec_line);

        if (std::string::npos != rec_line.find(target))
        {
            return true;
        }

        // 每次只保存一行的数据
        if ('\n' == rec_line.back())
        {
            rec_line.clear();
            count++;
        }
        // 输出超过wait_line行后，视为等待失败，退出该部分。
        if ((wait_line != 0) and (count > wait_line))
        {
            return false;
        }
    }
    return true;
}

/*
* 主循环函数
*/
void loop(cmdline::parser *cli_ptr)
{
    // 主板自动停止在u-boot中
    if (cli_ptr->exist("uboot"))
    {
        printf("u-boot auto stop\r\n");
        fflush(stdout);

        if (send_cmd_wait_response(cmd1, target1, 40, 0))
        {
            serial_handle->send_key_enter();
        }
        else
        {
            goto normal_read_write;
        }
    }

    for (const auto &cmd_info : cmd_infos)
    {
        if (cli_ptr->exist(cmd_info.name))
        {
            for (const auto &cmd : cmd_info.cmds)
            {
                if (!send_cmd_wait_response(cmd.send, cmd.receive, 0, 0))
                {
                    goto normal_read_write;
                }
                sleep(2);
            }
        }
    }

normal_read_write:
    serial_handle->set_keyboard_writable(true);
    std::string rec_line;
    while (SYSTEM_RUNNING)
    {
        serial_handle->read_data(rec_line);
    }
}

int main(int argc, char *argv[])
{
    // 读取配置文件
    read_command_config_file();

    cmdline::parser cli;
    cli.add<std::string>("port", 'p', "serial device", false, "/dev/ttyUSB0");
    cli.add<std::string>("baudrate", 'b', "baudrate", false, "115200", cmdline::oneof<std::string>("9600", "115200"));
    cli.add<std::string>("xmodem", 'x', "xmodem file", false, "./u-boot-spl.bin");
    cli.add<std::string>("ymodem", 'y', "ymodem file", false, "./u-boot.bin");
    cli.add("uboot", 'u', "auto stop in uboot");

    // 根据配置文件，添加命令
    for (const auto &cmd_info : cmd_infos)
    {
        cli.add(cmd_info.name, cmd_info.shortcut.c_str()[0], cmd_info.brief);
    }

    cli.add("version", 'v', "show version");
    cli.add("help", 'h', "print this message");
    cli.set_program_name("tiny-serial-terminal");

    if (!cli.parse(argc, argv))
    {
        std::cerr << cli.error() << std::endl
                  << cli.usage();
        return 0;
    }

    if (cli.exist("help"))
    {
        std::cerr << cli.usage();
        return 0;
    }

    std::cout << "Open serial device: "
              << cli.get<std::string>("port")
              << ":"
              << cli.get<std::string>("baudrate")
              << "\r\n";

    // 串口对象
    serial_handle = new SerialControl(cli.get<std::string>("port"), cli.get<std::string>("baudrate"), &SYSTEM_RUNNING);
    serial_handle->connect();
    serial_handle->set_keyboard_writable(false);
    serial_handle->set_receive_displayable(false);

    serial_handle->config(1, 0);

    std::thread r_thread(SerialControl::start_read_thread, serial_handle);
    std::thread w_thread(SerialControl::start_write_thread, serial_handle);

    // 通过 xymodem 传输文件
    if (cli.exist("xmodem"))
    {

        // xmodem 传输文件
        if (cli.exist("xmodem"))
        {
            std::cout << "start xmodem transfer\r\n";
            int ret = xymodem_send(serial_handle, cli.get<std::string>("xmodem"), PROTOCOL::PROTOCOL_XMODEM);
            if (ret < 0)
            {
                return ret;
            }
        }

    }

    if (cli.exist("ymodem"))
    {
        // ymodem 传输文件
        if (cli.exist("ymodem"))
        {
            std::cout << "start ymodem transfer\r\n";
            int ret = xymodem_send(serial_handle, cli.get<std::string>("ymodem"), PROTOCOL::PROTOCOL_YMODEM);
            if (ret < 0)
            {
                return ret;
            }
        }
    }

    serial_handle->set_receive_displayable(true);
    serial_handle->set_keyboard_writable(true);

    // 主循环
    loop(&cli);

    r_thread.join();
    w_thread.join();

    delete (serial_handle);

    std::cout << "Exit from Tiny-Serial-Terminal~~~" << std::endl
              << "\r\n";

    return 0;
}
